const std = @import("std");
const setting = @import("setting.zig");
const Cell = @import("cell.zig");

pub const GridIndex = struct {
    list: std.ArrayList(*GridCell),
    allocator: std.mem.Allocator,
    rows: usize,
    cols: usize,

    pub fn init(allocator: std.mem.Allocator) @This() {
        const cols: usize = setting.world_width / setting.ScanRangeMax + if (setting.world_width % setting.ScanRangeMax == 0) 0 else @as(usize, 1);
        const rows: usize = setting.world_height / setting.ScanRangeMax + if (setting.world_height % setting.ScanRangeMax == 0) 0 else @as(usize, 1);

        return .{
            .allocator = allocator,
            .list = std.ArrayList(*GridCell).init(allocator),
            .rows = rows,
            .cols = cols,
        };
    }

    pub fn index(self: *@This(), cell: *Cell) void {
        const _x: usize = @intFromFloat(std.math.floor(cell.x));
        const _y: usize = @intFromFloat(std.math.floor(cell.y));

        var col_idx: usize = 0;
        var row_idx: usize = 0;
        if (0 == _x) {
            col_idx = 0;
        } else {
            col_idx = (_x - 1) / setting.ScanRangeMax;
        }
        if (0 == _y) {
            row_idx = 0;
        } else {
            row_idx = (_y - 1) / setting.ScanRangeMax;
        }

        const idx: usize = row_idx * self.cols + col_idx;
        if (idx < self.list.items.len) {
            const item = self.list.items[idx];

            if (null == cell.grid_cell) {
                cell.grid_cell = item;
                item.add(cell);
            } else {
                if (cell.grid_cell != item) {
                    cell.grid_cell.?.remove(cell);

                    cell.grid_cell = item;
                    item.add(cell);
                }
            }
        }
    }

    pub fn setup(self: *@This()) !void {
        for (0..self.cols * self.rows) |_| {
            const grid_cell = try self.allocator.create(GridCell);
            GridCell.init(grid_cell, self);
            try self.list.append(grid_cell);
        }

        for (0..self.list.items.len) |idx| {
            var item = self.list.items[idx];

            var has_up = false;
            if (idx >= self.cols) {
                has_up = true;
                item.up = self.list.items[idx - self.cols];
            } else {
                has_up = false;
            }

            const has_right = if (idx % self.cols == self.cols - 1) false else true;
            const right_idx: usize = idx + 1;
            if (has_right) {
                item.right = self.list.items[right_idx];
            }

            const has_left = if (idx % self.cols == 0) false else true;
            if (has_left) {
                item.left = self.list.items[idx - 1];
            }

            var has_down = false;
            const down_idx = idx + self.cols;
            if (down_idx < self.list.items.len) {
                has_down = true;
                item.down = self.list.items[down_idx];
            } else {
                has_down = false;
            }

            if (has_up) {
                if (has_right) {
                    item.right_up = self.list.items[right_idx - self.cols];
                }

                if (has_left) {
                    item.left_up = self.list.items[idx - 1 - self.cols];
                }
            }

            if (has_down) {
                if (has_right) {
                    item.right_down = self.list.items[right_idx + self.cols];
                }

                if (has_left) {
                    item.left_down = self.list.items[idx - 1 + self.cols];
                }
            }
        }
    }

    pub fn deinit(self: *@This()) void {
        for (self.list.items) |item| {
            item.deinit();
            self.allocator.destroy(item);
        }
        self.list.deinit();
    }
};

pub const GridCell = struct {
    up: ?*GridCell,
    right_up: ?*GridCell,
    right: ?*GridCell,
    right_down: ?*GridCell,
    down: ?*GridCell,
    left_down: ?*GridCell,
    left: ?*GridCell,
    left_up: ?*GridCell,
    scanned_up: bool,
    scanned_right_up: bool,
    scanned_right: bool,
    scanned_right_down: bool,
    scanned_down: bool,
    scanned_left_down: bool,
    scanned_left: bool,
    scanned_left_up: bool,
    cell_list: std.ArrayList(*Cell),
    index: *GridIndex,

    pub fn init(self: *@This(), index: *GridIndex) void {
        self.* = .{
            .up = null,
            .right_up = null,
            .right = null,
            .right_down = null,
            .down = null,
            .left_down = null,
            .left = null,
            .left_up = null,
            .scanned_up = false,
            .scanned_right_up = false,
            .scanned_right = false,
            .scanned_right_down = false,
            .scanned_down = false,
            .scanned_left_down = false,
            .scanned_left = false,
            .scanned_left_up = false,
            .index = index,
            .cell_list = std.ArrayList(*Cell).init(index.allocator),
        };
    }

    pub fn remove(self: *@This(), cell: *Cell) void {
        for (0..self.cell_list.items.len) |i| {
            if (self.cell_list.items[i] == cell) {
                _ = self.cell_list.swapRemove(i);
                break;
            }
        }
    }

    pub fn add(self: *@This(), cell: *Cell) void {
        self.cell_list.append(cell) catch {};
    }

    pub fn deinit(self: *@This()) void {
        for (self.cell_list.items) |item| {
            item.deinit();
            self.cell_list.allocator.destroy(item);
        }
        self.cell_list.deinit();
    }
};
